import requests
import json
import re
from lxml import etree
import random
import ddddocr
import aiohttp
import asyncio
import aiofiles
import os

module_src = os.path.abspath(__file__)
module_dir = os.path.dirname(module_src)


class SessionManager:
    """
    会话维持类

    Args:
        role (string): 取值`Buyer`或`Seller`
    """

    def __init__(self, role):
        self._role = role
        self._data = None
        self.cookies = None
        self.session = requests.Session()  # 创建共用会话
        self.session.headers.update(
            {  # 更新请求头
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
                "Referer": "https://www.ksm.com.cn/",
            }
        )
        self.init_login()

    def init_login(self):
        """
        初始化登录流程,获取登录session
        """

        self._select_role()
        key = self._get_client_key()
        self._post_redirect(key)

    def get_data(self):
        return self._data

    def _select_role(self):  # 选择登录身份
        """
        选择登录身份
        """
        with open(f"{module_dir}/user.json", "r") as f:
            all_user = json.load(f)
            self._data = all_user[self._role]

    def _get_verify_code(self):  # 获取验证码结果
        """
        获取验证码结果
        """
        VERYFY_URL = "https://sso.casmart.com.cn/verify-code"
        res_verify = self.session.get(url=VERYFY_URL)  # 请求验证码
        if res_verify.status_code == 200:
            cookies = requests.utils.dict_from_cookiejar(res_verify.cookies)
            print(cookies)
            ocr = ddddocr.DdddOcr(show_ad=False)
            verify_code = ocr.classification(res_verify.content)
            print(f"VerifyCode: {verify_code}")
            return verify_code

    def _get_client_key(self):  # 获取登录凭证
        """
        获取登录凭证,并获取`client_key`
        """
        LOGIN_URL = "https://sso.casmart.com.cn/login"

        heardes = {  # 请求头
            "Origin": "https://sso.casmart.com.cn/",
            "content-type": "application/json",
        }

        user_data = {  # 登录参数
            "channelId": self._data["channelId"],
            "returnUrl": "http://supplier.casmart.com.cn/",
            "userName": self._data["userName"],
            "password": self._data["password"],
            "verifycode": self._get_verify_code(),
        }

        response = self.session.post(
            url=LOGIN_URL, headers=heardes, data=json.dumps(user_data)
        )

        if response.status_code == 200:
            print(response.json())
            print(response.cookies)
            return response.json()["data"]
        else:
            raise Exception("Get Login Session Failed")

    def _post_redirect(self, client_key):
        """
        携带`client_key`请求后会自动跳转到`returnUrl`地址并设置`cookie`
        """
        REDIRECT_URL = "https://sso.casmart.com.cn/redirect"

        data = {
            "returnUrl": self._data["returnUrl"],
            "channelId": self._data["channelId"],
            "clientKey": client_key,
        }
        print("请求参数")
        print(data)
        response = self.session.post(url=REDIRECT_URL, data=data)
        if response.status_code == 200:
            print(response.cookies)
            self.cookies = {cookie.name: cookie.value for cookie in response.cookies}
            print(self.cookies)
        else:
            raise Exception("Get Client Session Failed")


class RequestHanlder(SessionManager):  # 请求类 继承于`SessionManager`
    """
    请求类 继承于`SessionManager`

    Args:
        role (string): 取值`Buyer`或`Seller`
    """

    def __init__(self, role):
        super().__init__(role)
        self._sem = 3  # 并发数量
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
            "Referer": "https://supplier.casmart.com.cn/",
        }
        # self.async_session = aiohttp.ClientSession(
        #     headers=headers, cookies=self.cookies
        # )

    def seller_product_list(self):
        SEARCH_URL = "https://supplier.casmart.com.cn/product/basic/list"

        # region 搜索测试
        json_data = {
            "page": 2,
            "count": 10,
            "params": {
                "brandName": "",
                "cateId": "",
                "tabIndex": "1",
                "stationId": "",
                "brandId": "300154921",
                "deliveryId": "",
                "searchType": "1",
                "search": "抗体",
            },
        }
        # endregion

        resp = self.session.post(url=SEARCH_URL, json=json_data)
        print("搜索测试")
        print(resp.json())

    async def async_fetch(self, page_num):
        """
        异步网页请求器
        """
        max_retries = 3  # 最大重试次数
        backoff_factor = 1  # 重试间隔
        attempt = 0  # 重试次数

        # region 搜索配置
        # 店铺商品列表
        SEARCH_URL = "https://a0002100.casmart.com.cn/shop/products"

        # params = {
        #     "queryString": "biolegend",
        #     "queryType": "pro",
        #     "minprice": "",
        #     "maxprice": "",
        #     "supplierCateId": "-1"
        # }

        data = {
            "initPage": True,
            "pageNum": page_num,
            "pageSize": 20,
            "orderBy": "hotsale_down",
            "supplierCateId": "-1",
            "queryString": "",
            "minPrice": None,
            "maxPrice": None,
            "queryType": "pro",
        }
        # endregion

        while attempt < max_retries:
            try:
                async with asyncio.Semaphore(self._sem):  # 信号量控制
                    async with self.async_session.post(
                        url=SEARCH_URL, json=data, timeout=50
                    ) as response:  # 设置超时

                        page_list = await response.json()
                        return self.delete_buyyer_list_attrs(page_list)

            except asyncio.TimeoutError:
                print(f"Timeout fetching {page_num}")
            except aiohttp.ClientError as e:
                print(f"Error fetching {page_num}: {e}")

            finally:
                attempt += 1
                delay = backoff_factor * (2**attempt) + random.uniform(0, 1)
                await asyncio.sleep(delay)

        return {"page": page_num, "error": "Max retries exceeded"}

    async def async_product_fetch(self, proid):
        """
        异步获取网页商品描述和图片信息
        """

        max_retries = 5  # 最大重试次数
        backoff_factor = 1  # 重试间隔
        attempt = 0  # 重试次数

        BASE_URL = "https://www.casmart.com.cn/product-details/page/300091850/" + str(
            proid
        )
        while attempt < max_retries:
            try:
                async with asyncio.Semaphore(self._sem):  # 信号量控制
                    async with self.async_session.get(
                        url=BASE_URL, timeout=15
                    ) as response:  # 设置超时

                        # 获取HTML
                        page_text = await response.text()

                        # 商品详情
                        element = etree.HTML(page_text)

                        try:
                            content = element.xpath('//*[@id="productDetails"]')[0]
                            pro_des = etree.tostring(content[0], encoding="unicode")
                        except (IndexError, Exception) as e:
                            pro_des = ""

                        # 图片链接
                        temp_img_list = element.xpath('//*[@id="subpic_1"]/@onclick')

                        def get_img_url(text):
                            pattern = r"'(https?://[^']+)'"
                            match = re.search(pattern, text)
                            if match:
                                return match.group(1)
                            else:
                                return ""

                        img_list = [get_img_url(img) for img in temp_img_list]
                        return {"proId": proid, "imgList": img_list, "proDes": pro_des}

            except asyncio.TimeoutError:
                print(f"Timeout fetching {proid}")
            except aiohttp.ClientError as e:
                print(f"Error fetching {proid}: {e}")
            attempt += 1
            delay = backoff_factor * (2**attempt) + random.uniform(0, 3)
            await asyncio.sleep(delay)

        return {"page": proid, "error": "Max retries exceeded"}

    async def async_close(self):
        """
        关闭异步会话
        """
        if not self.async_session.closed:
            await self.async_session.close()

    async def async_seller_fetch(self, page_num):
        """
        异步获取卖家信息
        """
        max_retries = 3  # 最大重试次数
        backoff_factor = 1  # 重试间隔
        attempt = 0  # 重试次数
        SEARCH_URL = "https://supplier.casmart.com.cn/product/basic/list"
        data = {
            "page": page_num,
            "count": 10,
            "params": {
                "brandName": "",
                "cateId": "",
                "tabIndex": "1",
                "stationId": "",
                "brandId": "780",
                "deliveryId": "",
                "searchType": "0",
                "search": "",
            },
        }
        while attempt < max_retries:
            try:
                async with asyncio.Semaphore(self._sem):  # 信号量控制
                    async with self.async_session.post(
                        url=SEARCH_URL, json=data, timeout=50
                    ) as response:  # 设置超时
                        page_list = await response.json()
                        # return await response.json()
                        return self.delete_seller_list_attrs(page_list)

            except asyncio.TimeoutError:
                print(f"Timeout fetching {page_num}")
            except aiohttp.ClientError as e:
                print(f"Error fetching {page_num}: {e}")
            attempt += 1
            delay = backoff_factor * (2**attempt) + random.uniform(0, 1)
            await asyncio.sleep(delay)

        return {"page": page_num, "error": "Max retries exceeded"}

    def delete_seller_list_attrs(self, data: dict) -> dict:
        """
        删除卖家商品列表无用属性
        """

        for item in data["data"]["results"]:  # 循环获取商品
            del item["addTime"]
            del item["auditStatusInfo"]
            del item["bissnessType"]
            del item["deliveryCycle"]
            del item["hotSaleStatus"]
            del item["insaleStatus"]
            del item["insaleStatusInfo"]
            del item["modified"]
            del item["noStock"]
            del item["saleAmount"]
            del item["tempPrice"]
            del item["unSaleTime"]
        return data


async def main():
    """
    异步任务启动器
    """
    roler = "Seller"  # Buyer || Seller
    ex1 = RequestHanlder(roler)

    # tasks = [ex1.async_product_fetch(page_num) for page_num in range(1, 500)]
    tasks = [ex1.async_fetch(page) for page in range(1401, 1501)]
    results = await asyncio.gather(*tasks)
    await ex1.async_close()
    with open("./1500data.json", "w", encoding="utf-8") as f:
        f.write(json.dumps(results, ensure_ascii=False))
    print("任务完成")


if __name__ == "__main__":
    roler = "Seller"  # Buyer || Seller
    ex1 = RequestHanlder(roler)
    asyncio.run(main())
